{
  Copyright 2012 Sergey Ostanin

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
}

unit Shell;

interface

uses
  Classes, SysUtils, Windows, FileUtil, Forms, Controls, Graphics, Dialogs,
  Transmitter, Scene, TestDocumentEditor, VisualUtils, LCLType, ActnList,
  Dispatcher, MiscUtils, shfolder, superobject, ComCtrls, LocalProcessTransmitter,
  UnicodeCommandLine, Messages, zstreamCustom, TestUtils, CrcUtils, ExeTestUtils,
  base64;

type
  TShellForm = class(TForm)
    actActivateNextFile: TAction;
    actActivatePreviousFile: TAction;
    alMain: TActionList;
    ApplicationProperties: TApplicationProperties;
    procedure actActivateNextFileExecute(Sender: TObject);
    procedure actActivatePreviousFileExecute(Sender: TObject);
    procedure ApplicationPropertiesEndSession(Sender: TObject);
    procedure ApplicationPropertiesIdle(Sender: TObject; var Done: Boolean);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormShow(Sender: TObject);
  private type
    TOpenFile = class(TComponent)
    private
      FId: String;
      FFileName: String;
      FScene: TScene;
      FDispatcher: TDispatcher;
      FEditor: TTestDocumentEditorFrame;
      FSelectedControlPath: TStringList;
      FAutoCreated: Boolean;
    public
      constructor Create; reintroduce;
      destructor Destroy; override;
      function Title: String;
      function IsNew: Boolean;
      function IsImported: Boolean;
    end;
    TOpenFileList = TGenericObjectList<TOpenFile>;
  private
    FTransmitter: TTransmitter;
    FOpenFiles: TOpenFileList;
    FCurrentFileIndex: Integer;
    FDispatcher: TDispatcher;
    FFileBar: TToolBar;
    FInitialized: Boolean;
    procedure Render;
    procedure LoadOpenFileList(out CurrentFileIndex: Integer);
    procedure SaveOpenFileList;
    procedure CreateEditor(f: TOpenFile);
    function CurrentFile: TOpenFile;
    procedure SelectControlByPath(Path: TStrings);
    procedure SelectFile(FileIndex: Integer);
    procedure TestSave(Sender: TTestDocumentEditorFrame);
    function SaveTestFile(f: TOpenFile): Boolean;
    function FileNameToOpenFileIndex(const FileName: String): Integer;
    procedure TestOpen(Sender: TTestDocumentEditorFrame);
    procedure OpenTestFile(const FileName: String);
    procedure TestCreate(Sender: TTestDocumentEditorFrame);
    function GetUntitledFileName: String;
    procedure ShowCurrentFileTitle;
    procedure TestClose(Sender: TTestDocumentEditorFrame);
    function CloseCurrentFile: Boolean;
    procedure CreateFileBar;
    procedure AddToFileBar(f: TOpenFile);
    procedure ShowFileBar;
    procedure SelectOpenFile(f: TOpenFile);
    procedure FileButtonClick(Sender: TObject);
    procedure FileButtonMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure UpdateFileButton(b: TToolButton);
    procedure TestExit(Sender: TTestDocumentEditorFrame);
    procedure WMCopyData(var Message: TMessage); message WM_COPYDATA;
    procedure TestExportToExe(Sender: TTestDocumentEditorFrame);
    procedure ExportToExe(f: TOpenFile);
    { private declarations }
  public const
    COPY_DATA_ID_OPEN_FILE = $ab307cfa;
  public
    class function Open: TShellForm;
    constructor Create(Owner: TComponent); override;
    destructor Destroy; override;
    { public declarations }
  end; 

implementation

{$R *.lfm}

resourcestring
  SWindowTitle = '%s - Iren';
  SOpenTestFilter = 'Tests (*.it3; *.it2; *.exe)|*.it3;*.it2;*.exe|All Files|*.*';
  SOpenTest = 'Open';
  STestOpenError = 'Can''t open file.';
  SInvalidFileName = 'Incorrect file name: "%s".';
  SUntitled = 'Untitled %d';
  SSaveTestFilter = 'Tests (*.it3)|*.it3|All Files|*.*';
  SSaveTest = 'Save';
  SConfirmation = 'Confirmation';
  SSaveChanges = 'Save changes in "%s"?';
  SSaveButton = 'Save';
  SDontSaveButton = 'Don''t Save';
  STestRestoreError = 'Can''t restore file "%s".';
  SExportTestToExeFilter = 'Programs (*.exe)|*.exe|All Files|*.*';

const
  ROOT_ID = '00000000000000000000000000000000';
  FILE_PROCESSOR_CHANNEL_ID = '1f35e6303309d98fce28595877773de6';
  DATA_DIRECTORY = 'irenproject.ru';
  OPEN_FILES_DAT = DATA_DIRECTORY + DirectorySeparator + 'openFiles.dat';
  STDERR_FILE = DATA_DIRECTORY + DirectorySeparator + 'server.log';

function GetApplicationDataDirectory: String;
const
  SHGFP_TYPE_CURRENT = 0;
var
  Buffer: array [0..MAX_PATH] of UnicodeChar;
begin
  if SHGetFolderPathW(0, CSIDL_APPDATA or CSIDL_FLAG_CREATE, 0, SHGFP_TYPE_CURRENT, Buffer) <> S_OK then
    raise Exception.Create('SHGetFolderPath failed.');
  Result := UTF8Encode(UnicodeString(Buffer));
end;

function GetOpenFilesDatName: String;
begin
  Result := GetApplicationDataDirectory + DirectorySeparator + OPEN_FILES_DAT;
end;

procedure GetControlPath(Container, Control: TWinControl; Path: TStrings);
var
  c: TWinControl;
begin
  Path.Clear;
  c := Control;

  while (c <> nil) and (c <> Container) do
  begin
    if c.Name = '' then
      c := nil
    else
    begin
      Path.Add(c.Name);
      c := c.Parent;
    end;
  end;

  if c = nil then
    Path.Clear;
end;

function GetControlByPath(Container: TWinControl; Path: TStrings): TWinControl;
var
  c: TControl;
  i: Integer;
begin
  Result := Container;
  for i := Path.Count-1 downto 0 do
  begin
    c := Result.FindChildControl(Path[i]);
    if c is TWinControl then
      Result := TWinControl(c)
    else
    begin
      Result := nil;
      Break;
    end;
  end;
end;

procedure CheckFileName(const s: String);
begin
  if ContainsSpecialCharacter(s) then
    raise Exception.CreateFmt(SInvalidFileName, [s]);
end;

procedure OrderToolButtons(ToolBar: TToolBar);
var
  b: TToolButton;
begin
  for b in ToolBar do
    b.Left := MaxInt div 2;
end;

{ TShellForm.TOpenFile }

constructor TShellForm.TOpenFile.Create;
begin
  inherited Create(nil);
  FSelectedControlPath := TStringList.Create;
end;

destructor TShellForm.TOpenFile.Destroy;
begin
  FreeAndNil(FEditor);
  FreeAndNil(FDispatcher);
  FreeAndNil(FScene);
  FreeAndNil(FSelectedControlPath);
  inherited;
end;

function TShellForm.TOpenFile.Title: String;
begin
  Result := ExtractFileNameOnly(FFileName);
end;

function TShellForm.TOpenFile.IsNew: Boolean;
begin
  Result := (Pos('\', FFileName) = 0) and (Pos('/', FFileName) = 0);
end;

function TShellForm.TOpenFile.IsImported: Boolean;
var
  s: String;
begin
  s := UpperCase(RightStr(FFileName, 4));
  Result := (s = '.IT2') or (s = '.EXE');
end;

{ TShellForm }

class function TShellForm.Open: TShellForm;
begin
  Application.CreateForm(TShellForm, Result);
end;

constructor TShellForm.Create(Owner: TComponent);
var
  ServerDir, ClassesDir, ClassPath, DebugOptions: String;
  ClassesDirExists: Boolean;
begin
  inherited;
  FOpenFiles := TOpenFileList.Create;
  FCurrentFileIndex := -1;

  CreateDirUTF8(GetApplicationDataDirectory + DirectorySeparator + DATA_DIRECTORY);

  ServerDir := GetExeDirectory + DirectorySeparator + 'server';
  ClassesDir := ServerDir + DirectorySeparator + 'compiled-classes';
  ClassesDirExists := DirectoryExistsUTF8(ClassesDir);

  if ClassesDirExists then
    ClassPath := ClassesDir + PathSeparator
  else
    ClassPath := '';
  ClassPath := ClassPath + ServerDir + DirectorySeparator + '*' + PathSeparator;

  if ClassesDirExists then
    DebugOptions := '-agentlib:jdwp=transport=dt_socket,server=y,address=127.0.0.1:12321,suspend=n,quiet=y '
  else
    DebugOptions := '';

  FTransmitter := TLocalProcessTransmitter.Create(
    ServerDir + DirectorySeparator + 'bin' + DirectorySeparator + 'irenServer.exe',
    Format('%s-Xverify:none -Xmx256m -cp "%s" "-Djava.library.path=%s" ru.irenproject.Main',
    [DebugOptions, ClassPath, ServerDir]),
    GetApplicationDataDirectory + DirectorySeparator + STDERR_FILE);
  FDispatcher := TDispatcher.Create(nil, FTransmitter, FILE_PROCESSOR_CHANNEL_ID);
  SetProcessShutdownParameters($3ff, 0); { We want to receive the shutdown notification early,
    so that we can ask the server process to quit. Otherwise it will be forcibly terminated by Windows. }

  Width := ScalePixels(900);
  Height := ScalePixels(600);
end;

destructor TShellForm.Destroy;
begin
  FreeAndNil(FFileBar);
  FreeAndNil(FDispatcher);
  FreeAndNil(FTransmitter);
  FreeAndNil(FOpenFiles);
  inherited;
end;

procedure TShellForm.Render;
var
  ActiveControlPath: TStrings;
begin
  ActiveControlPath := TStringList.Create;
  try
    GetControlPath(CurrentFile.FEditor, ActiveControl, ActiveControlPath);

    CurrentFile.FEditor.TabStop := TRUE;
    try
      CurrentFile.FEditor.Render;
    finally
      CurrentFile.FEditor.TabStop := FALSE;
    end;

    if ActiveControl = CurrentFile.FEditor then
      SelectControlByPath(ActiveControlPath);
  finally
    ActiveControlPath.Free;
  end;
end;

procedure TShellForm.LoadOpenFileList(out CurrentFileIndex: Integer);
var
  fs: TUnicodeFileStream;
  sl: TStringList;
  i: Integer;
  OpenFile: TOpenFile;

  function ReadLine: String;
  begin
    if i < sl.Count then
    begin
      Result := sl[i];
      Inc(i);
    end
    else
      Result := '';
  end;

begin
  CurrentFileIndex := -1;

  try
    fs := TUnicodeFileStream.OpenForRead(GetOpenFilesDatName);
  except on E: EFOpenError do
    fs := nil;
  end;

  if fs <> nil then
  begin
    try
      sl := TStringList.Create;
      try
        sl.LoadFromStream(fs);

        i := 0;
        while i < sl.Count do
        begin
          OpenFile := FOpenFiles.AddSafely(TOpenFile.Create);
          OpenFile.FId := ReadLine;
          OpenFile.FFileName := ReadLine;
          if StringToBoolean(ReadLine) then
            CurrentFileIndex := FOpenFiles.Count-1;

          while (i < sl.Count) and (sl[i] <> '') do
            Inc(i);
          while (i < sl.Count) and (sl[i] = '') do
            Inc(i);
        end;
      finally
        sl.Free;
      end;
    finally
      fs.Free;
    end;
  end;
end;

procedure TShellForm.SaveOpenFileList;
var
  Output: TOutputFile;
  f: TOpenFile;
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    for i := 0 to FOpenFiles.Count-1 do
    begin
      f := FOpenFiles[i];
      if sl.Count > 0 then
        sl.Add('');
      sl.Add(f.FId);
      sl.Add(f.FFileName);
      sl.Add(BooleanToString(i = FCurrentFileIndex));
    end;

    Output := TOutputFile.Create(GetOpenFilesDatName);
    try
      sl.SaveToStream(Output.Stream);
      Output.Commit;
    finally
      Output.Free;
    end;
  finally
    sl.Free;
  end;
end;

procedure TShellForm.CreateEditor(f: TOpenFile);
begin
  Assert( f.FScene = nil );
  f.FScene := TScene.Create;

  Assert( f.FDispatcher = nil );
  f.FDispatcher := TDispatcher.Create(f.FScene, FTransmitter, f.FId);

  f.FDispatcher.PerformAction(ROOT_ID, 'nop');
  f.FScene.ResetChanged;

  Assert( f.FEditor = nil );
  f.FEditor := TTestDocumentEditorFrame.Create(ROOT_ID, f.FDispatcher, Self);

  f.FEditor.OnSave := TestSave;
  f.FEditor.OnOpen := TestOpen;
  f.FEditor.OnCreate := TestCreate;
  f.FEditor.OnClose := TestClose;
  f.FEditor.OnExit := TestExit;
  f.FEditor.OnExportToExe := TestExportToExe;

  f.FEditor.Render;
  f.FEditor.Visible := FALSE;

  AddToFileBar(f);
end;

function TShellForm.CurrentFile: TOpenFile;
begin
  Assert( FCurrentFileIndex <> -1 );
  Result := FOpenFiles[FCurrentFileIndex];
end;

procedure TShellForm.SelectControlByPath(Path: TStrings);
var
  c: TWinControl;
begin
  c := GetControlByPath(CurrentFile.FEditor, Path);
  if (c <> nil) and c.CanFocus then
    ActiveControl := c;
end;

procedure TShellForm.SelectFile(FileIndex: Integer);
begin
  Assert( FileIndex >= 0 );
  Assert( FileIndex < FOpenFiles.Count );

  if FCurrentFileIndex <> FileIndex then
  begin
    if FCurrentFileIndex <> -1 then
    begin
      GetControlPath(CurrentFile.FEditor, ActiveControl, CurrentFile.FSelectedControlPath);
      CurrentFile.FEditor.Visible := FALSE;
    end;

    FCurrentFileIndex := FileIndex;

    ShowAlignedControlReliably(CurrentFile.FEditor);
    if CurrentFile.FSelectedControlPath.Count > 0 then
      SelectControlByPath(CurrentFile.FSelectedControlPath)
    else
      CurrentFile.FEditor.SelectFirst;

    ShowFileBar;
    FFileBar.Buttons[FCurrentFileIndex*2 + 1].Down := TRUE;
    ShowCurrentFileTitle;
  end;
end;

procedure TShellForm.TestSave(Sender: TTestDocumentEditorFrame);
begin
  SaveTestFile(CurrentFile);
end;

function TShellForm.SaveTestFile(f: TOpenFile): Boolean;
var
  d: TSaveDialog;
  Output: TOutputFile;
  FileName: String;
  SavedCursor: TCursor;
begin
  if f.IsNew or f.IsImported then
  begin
    d := TSaveDialog.Create(nil);
    try
      d.Options := d.Options + [ofHideReadOnly, ofNoChangeDir, ofOverwritePrompt];
      d.Filter := SSaveTestFilter;
      d.DefaultExt := 'it3';
      d.Title := SSaveTest;
      if f.IsImported then
        d.FileName := ExtractFileNameWithoutExt(f.FFileName);

      Result := d.Execute;
      FileName := d.FileName;
    finally
      d.Free;
    end;
  end
  else
  begin
    Result := TRUE;
    FileName := f.FFileName;
  end;

  if Result then
  begin
    SavedCursor := SetWaitCursor;
    try
      Output := TOutputFile.Create(FileName);
      try
        WriteStreamString(Output.Stream, f.FEditor.GetTestData);
        Output.Commit;
      finally
        Output.Free;
      end;

      f.FEditor.MarkAsUnmodified;
    finally
      Screen.Cursor := SavedCursor;
    end;

    if f.FFileName <> FileName then
    begin
      f.FFileName := FileName;
      UpdateFileButton(FFileBar.Buttons[FOpenFiles.IndexOf(f)*2 + 1]);
      if f = CurrentFile then
        ShowCurrentFileTitle;
      SaveOpenFileList;
    end;
  end;
end;

function TShellForm.FileNameToOpenFileIndex(const FileName: String): Integer;
begin
  for Result := 0 to FOpenFiles.Count-1 do
    if FOpenFiles[Result].FFileName = FileName then
      Exit;
  Result := -1;
end;

procedure TShellForm.TestOpen(Sender: TTestDocumentEditorFrame);
var
  d: TOpenDialog;
  FileName: String;
begin
  d := TOpenDialog.Create(nil);
  try
    d.Options := d.Options + [ofHideReadOnly, ofNoChangeDir, ofAllowMultiSelect];
    d.Filter := SOpenTestFilter;
    d.Title := SOpenTest;

    if d.Execute then
    begin
      for FileName in d.Files do
        OpenTestFile(FileName);
    end;
  finally
    d.Free;
  end;
end;

procedure TShellForm.OpenTestFile(const FileName: String);
var
  Input, Res: ISuperObject;
  f: TOpenFile;
  CreateNew: Boolean;
  FileIndex: Integer;
  SavedCursor: TCursor;
  ItStream: TMemoryStream;
  DummyTestTitle: String;
begin
  CreateNew := FileName = '';

  if CreateNew then
    FileIndex := -1
  else
    FileIndex := FileNameToOpenFileIndex(FileName);

  if FileIndex = -1 then
  begin
    Input := SO;
    if not CreateNew then
    begin
      CheckFileName(FileName);

      if UpperCase(RightStr(FileName, 4)) = '.EXE' then
      begin
        ItStream := TMemoryStream.Create;
        try
          ExtractItStreamFromExe(FileName, ItStream, DummyTestTitle);
          ItStream.Seek(0, soFromBeginning);
          Input.S['fileData'] := EncodeStringBase64(ReadStreamTail(ItStream));
        finally
          ItStream.Free;
        end;
      end
      else
        Input.S['fileName'] := FileName;
    end;

    try
      if not CreateNew then
        SavedCursor := SetWaitCursor;
      try
        Res := FDispatcher.PerformAction(ROOT_ID, 'open', Input);
      finally
        if not CreateNew then
          Screen.Cursor := SavedCursor;
      end;
    except on E: ECommandFailed do
      raise Exception.Create(STestOpenError);
    end;

    f := TOpenFile.Create;
    try
      f.FId := Res.S['fileId'];
      if CreateNew then
        f.FFileName := GetUntitledFileName
      else
        f.FFileName := FileName;
      CreateEditor(f);
    except
      f.Free;
      raise;
    end;
    FOpenFiles.AddSafely(f);
    SaveOpenFileList;

    if not CreateNew and (FOpenFiles.Count = 2) and (FCurrentFileIndex <> -1)
      and CurrentFile.FAutoCreated and CurrentFile.IsNew
      and not CurrentFile.FEditor.Modified then
      CloseCurrentFile;

    FileIndex := FOpenFiles.Count-1;
  end;

  SelectFile(FileIndex);
end;

procedure TShellForm.TestCreate(Sender: TTestDocumentEditorFrame);
begin
  OpenTestFile('');
end;

function TShellForm.GetUntitledFileName: String;
var
  n: Integer;
begin
  n := 0;
  repeat
    Inc(n);
    Result := Format(SUntitled, [n]);
  until FileNameToOpenFileIndex(Result) = -1;
end;

procedure TShellForm.ShowCurrentFileTitle;
var
  WindowTitle: String;
begin
  WindowTitle := Format(SWindowTitle, [CurrentFile.Title]);
  Caption := WindowTitle;
  Application.Title := WindowTitle;
end;

procedure RemoveFromParentAndRelease(Control: TControl);
begin
  Control.Parent := nil;
  Application.ReleaseComponent(Control);
end;

procedure TShellForm.TestClose(Sender: TTestDocumentEditorFrame);
begin
  CloseCurrentFile;
end;

function TShellForm.CloseCurrentFile: Boolean;
var
  Input: ISuperObject;
  OldIndex, NewIndex: Integer;
  OldFile, NewFile: TOpenFile;
  UserResponse: TModalResult;
begin
  if CurrentFile.FEditor.Modified then
  begin
    UserResponse := QuestionDlg(SConfirmation, Format(SSaveChanges, [CurrentFile.Title]), mtConfirmation,
      [mrYes, SSaveButton, mrNo, SDontSaveButton, mrCancel], 0);
    if UserResponse = mrYes then
      Result := SaveTestFile(CurrentFile)
    else
      Result := UserResponse = mrNo;
  end
  else
    Result := TRUE;

  if Result then
  begin
    Input := SO;
    Input.S['fileId'] := CurrentFile.FId;
    FDispatcher.PerformAction(ROOT_ID, 'close', Input);

    if FOpenFiles.Count >= 2 then
    begin
      OldIndex := FCurrentFileIndex;
      if OldIndex = FOpenFiles.Count-1 then
        NewIndex := OldIndex - 1
      else
        NewIndex := OldIndex + 1;

      OldFile := CurrentFile;
      NewFile := FOpenFiles[NewIndex];

      SelectFile(NewIndex);

      FOpenFiles.Extract(OldFile);
      Application.ReleaseComponent(OldFile);
      FCurrentFileIndex := FOpenFiles.IndexOf(NewFile);

      RemoveFromParentAndRelease(FFileBar.Buttons[OldIndex*2 + 1]);
      RemoveFromParentAndRelease(FFileBar.Buttons[OldIndex*2]);

      SaveOpenFileList;
    end
    else
    begin
      DeleteFileUTF8(GetOpenFilesDatName);
      Application.Terminate;
    end;
  end;
end;

procedure TShellForm.CreateFileBar;
begin
  Assert( FFileBar = nil );
  FFileBar := TToolBar.Create(nil);
  FFileBar.AutoSize := TRUE;
  FFileBar.ShowCaptions := TRUE;
  FFileBar.EdgeBorders := [];
  FFileBar.Wrapable := FALSE;
  FFileBar.Transparent := TRUE;
end;

procedure TShellForm.AddToFileBar(f: TOpenFile);
var
  s, b: TToolButton;
begin
  s := TToolButton.Create(FFileBar);
  s.Parent := FFileBar;
  s.Style := tbsSeparator;
  s.Grouped := TRUE;

  b := TToolButton.Create(FFileBar);
  b.Parent := FFileBar;
  b.Style := tbsCheck;
  b.Grouped := TRUE;
  b.Tag := PtrInt(f);
  UpdateFileButton(b);
  b.OnClick := FileButtonClick;
  b.OnMouseDown := FileButtonMouseDown;

  OrderToolButtons(FFileBar);
end;

procedure TShellForm.ShowFileBar;
begin
  FFileBar.Parent := CurrentFile.FEditor.ExtraPanel;
  OrderToolButtons(FFileBar);
end;

procedure TShellForm.SelectOpenFile(f: TOpenFile);
var
  n: Integer;
begin
  n := FOpenFiles.IndexOf(f);
  Assert( n <> -1 );
  SelectFile(n);
end;

procedure TShellForm.FileButtonClick(Sender: TObject);
begin
  SelectOpenFile(TOpenFile((Sender as TToolButton).Tag));
end;

procedure TShellForm.FileButtonMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Shift = [ssMiddle] then
  begin
    SelectOpenFile(TOpenFile((Sender as TToolButton).Tag));
    CloseCurrentFile;
  end;
end;

procedure TShellForm.UpdateFileButton(b: TToolButton);
var
  f: TOpenFile;
begin
  f := TOpenFile(b.Tag);
  b.Caption := f.Title;

  if f.IsNew then
    b.Hint := ''
  else
    b.Hint := f.FFileName;
end;

procedure TShellForm.TestExit(Sender: TTestDocumentEditorFrame);
begin
  SaveOpenFileList;
  Application.Terminate;
end;

procedure TShellForm.WMCopyData(var Message: TMessage);
var
  cds: PCopyDataStruct;
  FileName: String;
begin
  Application.Restore;

  {$HINTS OFF}
  cds := PCopyDataStruct(Message.lParam);
  {$HINTS ON}
  if FInitialized and IsWindowEnabled(Handle)
    and (cds.dwData = COPY_DATA_ID_OPEN_FILE) and (cds.cbData > 0) then
  begin
    SetString(FileName, cds.lpData, cds.cbData);
    OpenTestFile(FileName);
  end;
end;

procedure TShellForm.TestExportToExe(Sender: TTestDocumentEditorFrame);
begin
  ExportToExe(CurrentFile);
end;

procedure TShellForm.ExportToExe(f: TOpenFile);
var
  d: TSaveDialog;
  Output: TOutputFile;
  Player: TUnicodeFileStream;
  CompressionStream: TCompressionStream;
  p: Int64;
  SavedCursor: TCursor;
begin
  d := TSaveDialog.Create(nil);
  try
    d.Options := d.Options + [ofHideReadOnly, ofNoChangeDir, ofOverwritePrompt];
    d.Filter := SExportTestToExeFilter;
    d.DefaultExt := 'exe';
    d.Title := SSaveTest;
    if not f.IsNew then
      d.FileName := ExtractFileNameWithoutExt(f.FFileName);

    if d.Execute then
    begin
      SavedCursor := SetWaitCursor;
      try
        Output := TOutputFile.Create(d.FileName);
        try
          Player := TUnicodeFileStream.OpenForRead(GetExeDirectory + DirectorySeparator + 'irenPlayer.bin');
          try
            Output.Stream.CopyFrom(Player, 0);
          finally
            Player.Free;
          end;

          p := Output.Stream.Position;
          WriteStreamString(Output.Stream, TEST_DOCUMENT_MIME_TYPE);
          WriteStreamSizedString(Output.Stream, ExtractFileNameOnly(d.FileName));

          CompressionStream := TCompressionStream.Create(clMax, Output.Stream, TRUE, FALSE);
          try
            WriteStreamString(CompressionStream, f.FEditor.ExportTest);
            CompressionStream.Flush;
          finally
            CompressionStream.Free;
          end;

          WriteStreamInt64(Output.Stream, Output.Stream.Position - p);

          Output.Stream.Seek(0, soFromBeginning);
          WriteStreamCardinal(Output.Stream,
            ComputeStreamChunkCrc32(Output.Stream, Output.Stream.Size));

          Output.Commit;
        finally
          Output.Free;
        end;
      finally
        Screen.Cursor := SavedCursor;
      end;
    end;
  finally
    d.Free;
  end;
end;

procedure TShellForm.ApplicationPropertiesIdle(Sender: TObject; var Done: Boolean);
begin
  if (FCurrentFileIndex <> -1) and CurrentFile.FScene.Changed then
  begin
    CurrentFile.FScene.ResetChanged;
    Render;
  end;
end;

procedure TShellForm.FormCloseQuery(Sender: TObject; var CanClose: boolean);
begin
  Application.Restore;
  CanClose := FALSE;
  repeat until not CloseCurrentFile or Application.Terminated;
end;

procedure TShellForm.actActivateNextFileExecute(Sender: TObject);
begin
  SelectFile((FCurrentFileIndex + 1) mod FOpenFiles.Count);
end;

procedure TShellForm.actActivatePreviousFileExecute(Sender: TObject);
var
  n: Integer;
begin
  if FCurrentFileIndex = 0 then
    n := FOpenFiles.Count-1
  else
    n := FCurrentFileIndex - 1;
  SelectFile(n);
end;

procedure TShellForm.ApplicationPropertiesEndSession(Sender: TObject);
begin
  if FTransmitter <> nil then
    FTransmitter.Disconnect;
end;

procedure TShellForm.FormShow(Sender: TObject);
var
  SelectIndex, i: Integer;
  FileName: String;
begin
  try
    CreateFileBar;

    LoadOpenFileList(SelectIndex);
    i := 0;
    while i < FOpenFiles.Count do
    begin
      try
        CreateEditor(FOpenFiles[i]);
        Inc(i);
      except on E: ECommandFailed do
        begin
          Application.MessageBox(PChar(Format(STestRestoreError, [FOpenFiles[i].Title])),
            PChar(Application.Title), MB_OK or MB_ICONERROR);
          FOpenFiles.Delete(i);
          SelectIndex := -1;
        end;
      end;
    end;

    FileName := GetCommandLineArgument(1);
    if FileName = '' then
    begin
      if FOpenFiles.Count = 0 then
      begin
        OpenTestFile('');
        CurrentFile.FAutoCreated := TRUE;
      end;

      if SelectIndex = -1 then
        SelectIndex := 0;
      SelectFile(SelectIndex);
    end
    else
      OpenTestFile(MakeAbsoluteFileName(FileName));

    FInitialized := TRUE;
  except on E: Exception do
    begin
      Hide;
      Application.MessageBox(PChar(E.Message), PChar(Application.Title), MB_OK or MB_ICONERROR);
      Application.Terminate;
    end;
  end;
end;

end.

